package Sock;
use strict;
use warnings;
use utf8;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(SockCreate);

use EV;
use IO::Socket::INET;
#use IO::Socket::IP;
use JSON::XS;

use Util;
use Command qw(ProcessCommand);

# протокол обмена простой
# json-строка, конец пекета \0
my %fh_to_id;

my $con_count = 1;
my %con_all;

sub SockCreate {
	my ($addr,$port) = @_;
	my $sock = new IO::Socket::INET(
	#my $sock = new IO::Socket::IP(
			Listen => 100,
			LocalHost =>  $addr,
			LocalPort =>  $port,
			ReuseAddr => 1,
			Blocking => 0,
		);
	unless($sock){
		print "SOCK $@\n";
		return;
	}
	
	# слушаем основной сокет
	return EV::io $sock, EV::READ, \&stream_sock_handler;
}
sub stream_sock_handler {
	my ($w,$rm) = @_;
    my $new = $w->fh->accept;
    unless($new){
		if($main::params{debug}){
			print "Cannot accept '$!' '$@'\n";
		}
		return 0;
    }else{
		if($main::params{debug}){
			printf "Accepted from %s:%d\n",$new->peerhost, $new->peerport;
		}
	}
	$new->blocking(0);
    my $nw = EV::io $new, EV::READ, \&read_sock_handler;
	## сохраняем слушателя, буфер с принятыми данными

	$con_all{++$con_count} = $nw;
	$nw->data([$con_count,"",$new->peerhost]);
}
sub read_sock_handler {
	my ($w, $m) = @_;
	# Получили данные
	my $buf;
	my $fh = $w->fh;
	if($fh->opened){
		$fh->recv($buf,1024);
	}
	if($buf){
		# есть данные, цепляем к предыдущим если есть
		$w->data->[1] .= $buf;
		while(my $cmd = SockParse(\$w->data->[1])){
			$cmd->{ip} = $w->data->[2];
			my $ret = ProcessCommand($cmd,$fh,\&send_sock_message);
			send_sock_message($fh,$ret) if $ret;
		}
	}else{
		# если ничего не прочитали, значит все закрыто
		delete $con_all{$w->data->[0]};
	}
}
sub send_sock_message {
	my ($fh,$msg) = @_;
	my $status = delete $msg->{status} || 200;
	$msg->{ts} ||=ts();
	$msg->{rid} ||= -1;
	$msg->{type} ||= "response";
	my $body = encode_json($msg).chr(0);
	
	my $nw;
	if(my $id = $fh_to_id{$fh->fileno}){
		$nw = $con_all{$id};
		$nw->data->[1] .= $body;
	}else{
		$nw = EV::io $fh, EV::WRITE, \&write_sock_handler;

		$con_all{++$con_count} = $nw;
		$nw->data([$con_count,$body]);
	}
}
sub write_sock_handler {
	my ($w, $m) = @_;
	my $fh = $w->fh;
	if(my $str = $w->data->[1]){
		my $need = length($str);
		if($main::params{debug} > 1){
			print "SEND:\n",$str,"\n";
		}
		my $rs;
		if($fh->opened && $fh->connected){
			$rs = $fh->send($str);
		}
		if($rs){
			$need -= $rs;
			# если отправлено не все, то продолжаем ждать
			if($need > 0){
				substr($w->data->[1],0,$rs,"");
				return 0;
			}else{
				$w->data->[1] = "";
			}
		}
	}
	if($fh->fileno){
		delete $fh_to_id{$fh->fileno};
	}
	delete $con_all{$w->data->[0]};
}
sub SockParse {
	my ($rstr) = @_;
	my $i = index($$rstr,chr(0));
	if($i == -1){
		return 0;
	}
	my $str = substr($$rstr,0,$i,"");

	if($main::params{debug} > 1){
		print "RECV:\n$str\n";
	}
	substr($$rstr,0,1,"");
	my $msg = decode_json($str);
	return $msg;
}
1;
